﻿// === Copyright 2008-2009 Jamie Macia et al. ===
// Module:      FullAttackRoller
// Created:     11/18/2008
// Author:      Jamie Macia
// Description: Form that lists and rolls a group of attacks.  Allows user to save and load full attack schemes, and automatically
//      calculates damage with or without critical hits, at user specification.
// Revisions:
//      *jmacia 11/18 Issue 1: Created

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using JMacia.Gaming.d20.Core.Dice;


namespace JMacia.Gaming.d20.FullAttacker
{
    /// <summary>
    /// Form that lists and rolls a group of attacks.  Allows user to save and load full attack schemes, and automatically
    /// calculates damage with or without critical hits, at user specification.
    /// </summary>
    public partial class FullAttackRoller : Form
    {
        #region Private Members

        private List<AttackEntry> _attacks = new List<AttackEntry>();
        private string _fileName;
        
        #endregion

        #region Constructor

        /// <summary>
        /// Default constructor
        /// </summary>
        public FullAttackRoller()
        {
            InitializeComponent();
            AttackEntry attackEntry1 = new AttackEntry();
            this.attackEntryFlowPanel.Controls.Add(attackEntry1);
            this._attacks.Add(attackEntry1);
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Adds an AttackEntry to the form when the Add Row button is clicked
        /// </summary>
        /// <param name="sender">The object from which this event was triggered</param>
        /// <param name="e">Event arguments (not used)</param>
        private void OnAddRowClick(object sender, EventArgs e)
        {
            AttackEntry newEntry = new AttackEntry();
            newEntry.TabIndex = 1;
            _attacks.Add(newEntry);
            this.attackEntryFlowPanel.Controls.Add(newEntry);
        }

        /// <summary>
        /// Removes the last AttackEntry (if any) when the Remove Last button is clicked
        /// </summary>
        /// <param name="sender">The object from which this event was triggered</param>
        /// <param name="e">Event arguments (not used)</param>
        private void OnRemoveLastRowClick(object sender, EventArgs e)
        {
            if (_attacks.Count == 0)
            {
                return;
            }
            AttackEntry deleteEntry = _attacks[_attacks.Count - 1];
            _attacks.Remove(deleteEntry);
            this.attackEntryFlowPanel.Controls.Remove(deleteEntry);
            deleteEntry.Dispose();
        }

        /// <summary>
        /// Resets the AttackEntries to a single, blank one when the Reset button is clicked
        /// </summary>
        /// <param name="sender">The object from which this event was triggered</param>
        /// <param name="e">Event arguments (not used)</param>
        private void OnResetClick(object sender, EventArgs e)
        {
            Reset();
        }

        /// <summary>
        /// Rolls a full attack upon click of the Roll button
        /// </summary>
        /// <param name="sender">The object from which this event was triggered</param>
        /// <param name="e">Event arguments (not used)</param>
        private void OnRollFullAttackClick(object sender, EventArgs e)
        {
            StringBuilder fullAttackString = new StringBuilder();
            foreach (AttackEntry thisEntry in _attacks)
            {
                if (thisEntry.IncludeInRoll)
                {
                    fullAttackString.AppendLine(RollSingleAttackType(thisEntry));
                }//if it's checked to include in the roll
            }//foreach AttackEntry in the collection

            txtResult.Text = fullAttackString.ToString();
        }

        /// <summary>
        /// Closes the form when the user clicks the Close button or the Exit menuitem
        /// </summary>
        /// <param name="sender">The object from which this event was triggered</param>
        /// <param name="e">Event arguments (not used)</param>
        private void OnCloseClick(object sender, EventArgs e)
        {
            this.Close();
        }

        /// <summary>
        /// Saves the file when the Save or Save As menuitem is clicked.
        /// Save As always displays the Save File dialog, while Save only does if there is not a stored filename.
        /// </summary>
        /// <param name="sender">The object from which this event was triggered</param>
        /// <param name="e">Event arguments (not used)</param>
        private void OnSaveClick(object sender, EventArgs e)
        {
            SaveFile(saveAsToolStripMenuItem == sender as ToolStripMenuItem);
        }

        /// <summary>
        /// Opens a file when the Open menuitem is clicked
        /// </summary>
        /// <param name="sender">The object from which this event was triggered</param>
        /// <param name="e">Event arguments (not used)</param>
        private void OnOpenClick(object sender, EventArgs e)
        {
            OpenFile();
        }

        /// <summary>
        /// When the New menuitem is clicked, prompt to save, save if asked and reset the form (if the user does not cancel)
        /// </summary>
        /// <param name="sender">The object from which this event was triggered</param>
        /// <param name="e">Event arguments (not used)</param>
        private void OnNewClick(object sender, EventArgs e)
        {
            DialogResult result = MessageBox.Show("Save changes?", "Save", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
            switch (result)
            {
                case DialogResult.Yes:
                    if (SaveFile())
                    {
                        Reset();
                        _fileName = string.Empty;
                    }
                    break;
                case DialogResult.No:
                    Reset();
                    _fileName = string.Empty;
                    break;
                case DialogResult.Cancel:
                default:
                    break;
            }
        }

        /// <summary>
        /// When the form is about to close, check if the user wants to save.
        /// Allows save and exit, exit without saving, or do not exit.
        /// </summary>
        /// <param name="sender">The object from which this event was triggered</param>
        /// <param name="e">The event arguments.
        /// Setting e.Cancel to true makes the form stop closing.</param>
        private void OnFormClosing(object sender, FormClosingEventArgs e)
        {
            DialogResult result = MessageBox.Show("Save changes before exiting?", "Save", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
            switch (result)
            {
                case DialogResult.Yes:
                    e.Cancel = !SaveFile();
                    break;
                case DialogResult.Cancel:
                    e.Cancel = true;
                    break;
                case DialogResult.No:
                default:
                    break;
            }
        }

        #endregion

        #region Private Helper Methods

        /// <summary>
        /// Removes all AttackEntry controls and instantiates a new one in its original state.
        /// </summary>
        private void Reset()
        {
            this.attackEntryFlowPanel.Controls.Clear();
            _attacks.Clear();
            AttackEntry attackEntry1 = new AttackEntry();
            this.attackEntryFlowPanel.Controls.Add(attackEntry1);
            this._attacks.Add(attackEntry1);
        }

        /// <summary>
        /// Rolls the sub-full-attack represented by one AttackEntry.
        /// </summary>
        /// <param name="attackType">The entry to be rolled</param>
        /// <returns>A string representing all the attacks (and damage) results of this attack type</returns>
        private string RollSingleAttackType(AttackEntry attackType)
        {
            AttackRoll roller;
            DamageRoll damager;
            try
            {
                roller = attackType.Attacks;
                damager = attackType.Damage;
            }
            catch (ArgumentException e)
            {
                MessageBox.Show(this, e.Message, "Error", MessageBoxButtons.OK);
                return string.Empty;
            }

            List<AttackResult> results = roller.RollFullAttack((int)udAC.Value);

            int misses = 0, hits = 0, crits = 0, totalDamage = 0;

            StringBuilder typeString = new StringBuilder("Attacks for ");
            typeString.AppendLine(attackType.AttackType);

            foreach (AttackResult result in results)
            {
                typeString.AppendFormat(" {0} ({1}) ", result.ModifiedRoll, result.UnmodifiedRoll);
                if (result.UnmodifiedRoll == 1 || result.UnmodifiedRoll == 20)
                {
                    typeString.Append("automatically ");
                }
                int currentDamage = 0;
                List<int> damageDice = new List<int>();
                switch (result.Result)
                {
                    case AttackResultEnum.Miss:
                        misses++;
                        typeString.AppendLine("misses");
                        break;
                    case AttackResultEnum.Hit:
                        hits++;
                        typeString.Append("hits");
                        if (result.ThreatensCritical && chkAllowCrits.Checked)
                        {
                            typeString.AppendFormat(", {0} ({1}) fails to confirm", result.CritConfirmModifiedRoll, result.CritConfirmUnmodifiedRoll);
                        }

                        typeString.AppendLine();

                        currentDamage = damager.Roll(out damageDice);
                        break;
                    case AttackResultEnum.CriticalHit:
                        if (chkAllowCrits.Checked)
                        {
                            crits++;
                            typeString.AppendFormat("hits, {0} ({1}) confirms", result.CritConfirmModifiedRoll, result.CritConfirmUnmodifiedRoll);
                            typeString.AppendLine();
                            currentDamage = damager.RollCritical(out damageDice);
                        }
                        else
                        {
                            hits++;
                            typeString.AppendLine("hits");
                            currentDamage = damager.Roll(out damageDice);
                        }
                        break;
                }//switch on the result of the attack to increment the counters

                if (result.Result != AttackResultEnum.Miss)
                {
                    typeString.AppendFormat("  Damage: {0}(", currentDamage);
                    for (int i = 0; i < damageDice.Count; i++)
                    {
                        typeString.Append(damageDice[i]);
                        if (i < damageDice.Count - 1)
                        {
                            typeString.Append(",");
                        }
                    }
                    typeString.AppendLine(")");
                }

                totalDamage += currentDamage;
            }

            typeString.AppendFormat("{0} hit(s), ", hits);
            typeString.AppendFormat("{0} crit(s), ", crits);
            typeString.AppendFormat("{0} miss(es); ", misses);
            typeString.AppendFormat("total damage: {0}", totalDamage);
            typeString.AppendLine();

            return typeString.ToString();
        }

        /// <summary>
        /// Saves the attack scheme.  If it has been saved previously, the user is not prompted for a destination.
        /// </summary>
        /// <returns>True if the file was saved, False if the user canceled</returns>
        private bool SaveFile()
        {
            return SaveFile(false);
        }

        /// <summary>
        /// Saves the attack scheme.
        /// </summary>
        /// <param name="saveAs">If true, prompts the user for a save destination even if one already exists</param>
        /// <returns>True if the file was saved, false if the user canceled</returns>
        private bool SaveFile(bool saveAs)
        {
            if (string.IsNullOrEmpty(_fileName) || saveAs)
            {
                if (saveDialog.ShowDialog() != DialogResult.OK)
                {
                    return false;
                }
            }
            else
            {
                saveDialog.FileName = _fileName;
            }
            XmlSerializer serial = new XmlSerializer(typeof(List<AttackDefinition>));
            List<AttackDefinition> defs = new List<AttackDefinition>();
            foreach (AttackEntry entry in _attacks)
            {
                defs.Add(entry.Definition);
            }

            try
            {
                Stream fs = saveDialog.OpenFile();
                serial.Serialize(fs, defs);
                _fileName = saveDialog.FileName;
                fs.Flush();
                fs.Close();
                fs.Dispose();
            }
            catch (Exception x)
            {
                MessageBox.Show("Error saving data: " + x.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            return true;
        }

        /// <summary>
        /// Opens a file and loads it contents into AttackEntry controls
        /// </summary>
        private void OpenFile()
        {
            if (openDialog.ShowDialog() != DialogResult.OK)
            {
                return;
            }

            XmlSerializer serial = new XmlSerializer(typeof(List<AttackDefinition>));
            List<AttackDefinition> defs;
            try
            {
                Stream fs = openDialog.OpenFile();
                object o = serial.Deserialize(fs);
                defs = (List<AttackDefinition>)o;
                _attacks.Clear();
                foreach (AttackDefinition def in defs)
                {
                    AttackEntry entry = new AttackEntry();
                    entry.Definition = def;
                    _attacks.Add(entry);
                }
                attackEntryFlowPanel.Controls.Clear();
                attackEntryFlowPanel.Controls.AddRange(_attacks.ToArray());
                _fileName = openDialog.FileName;
                fs.Close();
                fs.Dispose();
            }
            catch (Exception x)
            {
                MessageBox.Show("Error opening file: " + x.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        
        #endregion
    }

}
